package com.ybox.common.core.utils.sign;

import com.alibaba.fastjson2.JSON;
import com.ybox.common.core.utils.StringUtils;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author ych
 */
public class SignUtil {

    private static final Logger log = LoggerFactory.getLogger(SignUtil.class);
    /**
     * 密钥
     */
    private static final String SECRET = "ych-YBox_0123456789";

    public static final String SIGN = "sign";

    public static final String CONTENT_MD5 = "content-md5";

    public static final String CA_DATE = "ca-date";

    public static final String X_CA_KEY = "x-ca-key";
    public static final String X_CA_TIMESTAMP = "x-ca-timestamp";
    public static final String X_CA_NONCE = "x-ca-nonce";

    public static final String SIGN_KEY = "apisign_";
    public static final String PER_FIX = "x-ca-";

    public static String signString(String stringToSign, String secret) {
        String signature = "";
        try {
            Mac hmacSha256 = Mac.getInstance("HmacSHA256");
            byte[] appSecretBytes = secret.getBytes(Charset.forName("UTF-8"));
            hmacSha256.init(new SecretKeySpec(appSecretBytes, 0, appSecretBytes.length, "HmacSHA256"));
            // stringToSign是提取出来的签名串
            byte[] md5Result = hmacSha256.doFinal(stringToSign.getBytes(Charset.forName("UTF-8")));
            signature = org.apache.commons.codec.binary.Base64.encodeBase64String(md5Result);
        } catch (NoSuchAlgorithmException e) {
            log.error("签名失败,无此加密算法");
        } catch (InvalidKeyException e) {
            log.error("签名失败，无效密钥");
        }
        return signature;
    }

    public static String md5(String source) {
        String md5Result = null;
        try {
            byte[] hash = org.apache.commons.codec.binary.StringUtils.getBytesUtf8(source);
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(hash);
            hash = messageDigest.digest();
            md5Result = Hex.encodeHexString(hash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return md5Result;
    }


    /**
     * 获取 请求头中以prefix开头的参数所拼接成的字符串
     *
     * @param headers 请求头
     * @param prefix  'x-ca-'
     * @returns {string}
     */
    public static String buildCanonicalHeaders(Map<String, String> headers, String prefix) {
        Set<String> setList = new TreeSet<>();
        Set<String> keys = headers.keySet();
        Map<String, Object> caHeaders = new TreeMap<>();
        Iterator<String> it = keys.iterator();
        while (it.hasNext()) {
            String key = it.next();
            String lowerKey = key.toLowerCase().trim();
            if (lowerKey.startsWith(prefix)) {
                setList.add(lowerKey);
                caHeaders.put(lowerKey, headers.get(key));
            }
        }

        /**
         * 返回key:value\n形式
         */
        StringBuilder canonical = new StringBuilder();
        for (String str : setList) {
            canonical.append(str + ":" + caHeaders.get(str) + "\n");
        }

        return canonical.toString();
    }


    /**
     * 构造签名字符串
     *
     * @param method  请求方法
     * @param path    请求路径
     * @param headers 请求头
     * @param data    请求参数
     * @returns {string}
     */
    static String composeStringToSign(String method, String path, Map<String, String> headers, Map<String, Object> data) {
        String contentMD5 = headers.get(SignUtil.CONTENT_MD5);
        String contentType = headers.get(HttpHeaders.CONTENT_TYPE);
        if (StringUtils.isEmpty(contentType)){
            contentType = MediaType.APPLICATION_JSON_UTF8_VALUE;
        }
        String date = headers.get(SignUtil.CA_DATE);
        String signHeaders = buildCanonicalHeaders(headers, PER_FIX);

        // 获取路径
        String pathUnescaped = path;
        String str = StringUtils.format("{}\n{}\n{}\n{}\n{}{}", method, contentMD5, contentType, date, signHeaders, pathUnescaped);

        // data不为空的时候才做拼接
        if (StringUtils.isNotEmpty(data)) {
            str += '\n' + paramsSortToString(data, "\n");
        }
        return str;
    }

    /**
     * 根据请求参数的key按ascii排序拼接字符串 格式: key=value\n
     *
     * @param data  请求参数
     * @param split 分隔符
     * @returns {*}
     */
    static String paramsSortToString(Map<String, Object> data, String split) {
        if (data.isEmpty()) {
            return "";
        }
        Set<String> params = new TreeSet<>();
        Set<String> keys = data.keySet();

        Iterator<String> it = keys.iterator();
        while (it.hasNext()) {
            String key = it.next();
            Object values = data.get(key);
            if (values instanceof String || values instanceof Integer || values instanceof Boolean) {
                params.add(StringUtils.format("{}={}", key, values));
            }
            if (values instanceof List) {
                List list = (List) data.get(key);
                list.forEach(e -> {
                    params.add(StringUtils.format("{}={}", key, values));
                });
            }

        }
        return String.join(split, params);
    }

    public static String signature(String method, String path, Map<String, String> headers, Map<String, Object> queries) {
        // 排除sign
        TreeMap<String,String> signHeaders = new TreeMap<>();
        signHeaders.putAll(headers);
        signHeaders.remove(SignUtil.SIGN);
        String signature = signString(composeStringToSign(method, path, signHeaders, queries), SECRET);
        // Authorization字段介绍
        return "CA " + signature;
    }

    public static void main(String[] args) {
        String result = signString("post\n" +
                "\n" +
                "application/json;charset=utf-8\n" +
                "Tue, 31 Jan 2023 03:40:04 GMT\n" +
                "x-ca-key:ych-YBox_0123456789\n" +
                "x-ca-nonce:13742\n" +
                "x-ca-timestamp:1675136393336\n" +
                "/auth/login\n" +
                "code=123\n" +
                "password=admin123\n" +
                "username=admin\n" +
                "uuid=198ee42e9b894dffa689061dc339487f",SECRET);
        System.out.println(result);
    }
}